﻿using FastEmit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Dapper.Sharding
{
    public class AutoSharding<T> : ISharding<T> where T : class
    {
        public AutoSharding(ITable<T>[] tableList) : base(tableList)
        {

        }

        private static readonly string _msg = "AutoSharding not support";

        #region base

        public override ITable<T> GetTableById(object id)
        {
            var taskList = TableList.Select(s =>
            {
                return s.ExistsAsync(id);
            });

            var result = Task.WhenAll(taskList).Result;
            for (int i = 0; i < result.Length; i++)
            {
                if (result[i])
                {
                    return TableList[i];
                }
            }
            return null;
        }


        public override async Task<ITable<T>> GetTableByIdAsync(object id)
        {
            var taskList = TableList.Select(s =>
            {
                return s.ExistsAsync(id);
            });

            var result = await Task.WhenAll(taskList);
            for (int i = 0; i < result.Length; i++)
            {
                if (result[i])
                {
                    return TableList[i];
                }
            }
            return null;
        }

        public override ITable<T> GetTableByModel(T model)
        {
            var accessor = FastEmitFactory.GetTypeAccessor(typeof(T));
            var id = accessor.GetValue(model, SqlField.PrimaryKey);
            return GetTableById(id);
        }

        public override Task<ITable<T>> GetTableByModelAsync(T model)
        {
            var accessor = FastEmitFactory.GetTypeAccessor(typeof(T));
            var id = accessor.GetValue(model, SqlField.PrimaryKey);
            return GetTableByIdAsync(id);
        }

        public override Dictionary<ITable<T>, List<object>> GetTableByGroupIds(object ids)
        {
            throw new Exception(_msg);
        }

        public override Dictionary<ITable<T>, List<T>> GetTableByGroupModelList(IEnumerable<T> modelList)
        {
            throw new Exception(_msg);
        }


        public ITable<T> _GetTableById(object id)
        {
            return TableList[ShardingUtils.Mod(id, TableList.Length)];
        }

        public ITable<T> _GetTableByModel(T model)
        {
            return TableList[ShardingUtils.Mod(model, SqlField.PrimaryKey, SqlField.PrimaryKeyType, TableList.Length)];
        }

        public Dictionary<ITable<T>, List<object>> _GetTableByGroupIds(object ids)
        {
            var dict = new Dictionary<ITable<T>, List<object>>();
            var idsList = CommonUtil.GetMultiExec(ids);
            if (idsList != null)
            {
                foreach (var id in idsList)
                {
                    var table = _GetTableById(id);
                    if (!dict.ContainsKey(table))
                    {
                        dict.Add(table, new List<object>());
                    }
                    dict[table].Add(id);
                }
            }
            return dict;
        }

        public Dictionary<ITable<T>, List<T>> _GetTableByGroupModelList(IEnumerable<T> modelList)
        {
            var dict = new Dictionary<ITable<T>, List<T>>();
            foreach (var item in modelList)
            {
                var table = _GetTableByModel(item);
                if (!dict.ContainsKey(table))
                {
                    dict.Add(table, new List<T>());
                }
                dict[table].Add(item);
            }
            return dict;
        }

        #endregion

        #region insert

        public override void Insert(T model, DistributedTransaction tran = null, int? timeout = null)
        {
            var tb = _GetTableByModel(model);
            tb.Insert(model, tran, timeout);
        }

        public override void Insert(IEnumerable<T> modelList, DistributedTransaction tran = null, int? timeout = null)
        {
            Wrap(tran, () =>
             {
                 var dict = _GetTableByGroupModelList(modelList);
                 foreach (var item in dict)
                 {
                     item.Key.Insert(item.Value, tran, timeout);
                 }
             });
        }

        public override void InsertIfNoExists(T model, DistributedTransaction tran = null, int? timeout = null)
        {
            if (!ExistsAsync(model).Result)
            {
                Insert(model);
            }
        }

        public override void InsertIfNoExists(IEnumerable<T> modelList, DistributedTransaction tran = null, int? timeout = null)
        {
            Wrap(tran, () =>
            {
                foreach (var item in modelList)
                {
                    InsertIfNoExists(item, tran);
                }
            });

        }

        public override void Merge(T model, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null)
        {
            if (ExistsAsync(model).Result)
            {
                Update(model, fields);
            }
            else
            {
                Insert(model);
            }
        }

        public override void Merge(IEnumerable<T> modelList, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null)
        {
            Wrap(tran, () =>
            {
                foreach (var item in modelList)
                {
                    Merge(item, fields, tran);
                }
            });
        }

        public override void MergeIgnore(T model, List<string> fields, DistributedTransaction tran = null, int? timeout = null)
        {
            if (ExistsAsync(model).Result)
            {
                UpdateIgnore(model, fields);
            }
            else
            {
                Insert(model);
            }
        }

        public override void MergeIgnore(IEnumerable<T> modelList, List<string> fields, DistributedTransaction tran = null, int? timeout = null)
        {
            Wrap(tran, () =>
            {
                foreach (var item in modelList)
                {
                    MergeIgnore(item, fields, tran);
                }
            });
        }

        #endregion

        #region inserAsync

        public override async Task InsertAsync(T model, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var tb = _GetTableByModel(model);
            await tb.InsertAsync(model, tran, timeout, cancellationToken);
        }

        public override async Task InsertAsync(IEnumerable<T> modelList, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                var dict = _GetTableByGroupModelList(modelList);
                foreach (var item in dict)
                {
                    await item.Key.InsertAsync(item.Value, tran, timeout, cancellationToken);
                }
            });
            await task;
        }

        public override async Task InsertIfNoExistsAsync(T model, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            if (!await ExistsAsync(model, cancellationToken))
            {
                await InsertAsync(model, cancellationToken: cancellationToken);
            }
        }

        public override async Task InsertIfNoExistsAsync(IEnumerable<T> modelList, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = WrapAsync(tran, async () =>
            {
                foreach (var item in modelList)
                {
                    await InsertIfNoExistsAsync(item, tran, cancellationToken: cancellationToken);
                }
            });
            await task;
        }

        public override async Task MergeAsync(T model, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            if (await ExistsAsync(model, cancellationToken))
            {
                await UpdateAsync(model, fields, cancellationToken: cancellationToken);
            }
            else
            {
                await InsertAsync(model, cancellationToken: cancellationToken);
            }
        }

        public override async Task MergeAsync(IEnumerable<T> modelList, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = WrapAsync(tran, async () =>
            {
                foreach (var item in modelList)
                {
                    await MergeAsync(item, fields, tran, cancellationToken: cancellationToken);
                }
            });
            await task;
        }

        public override async Task MergeIgnoreAsync(T model, List<string> fields, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            if (await ExistsAsync(model, cancellationToken))
            {
                await UpdateIgnoreAsync(model, fields, cancellationToken: cancellationToken);
            }
            else
            {
                await InsertAsync(model, cancellationToken: cancellationToken);
            }
        }

        public override async Task MergeIgnoreAsync(IEnumerable<T> modelList, List<string> fields, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                foreach (var item in modelList)
                {
                    await MergeIgnoreAsync(item, fields, tran, cancellationToken: cancellationToken);
                }
            });
            await task;
        }

        #endregion

        #region update

        public override int Update(T model, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null)
        {
            int count = 0;
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     count += item.Update(model, fields, tran, timeout);
                 }
             });
            return count;
        }

        public override void Update(IEnumerable<T> modelList, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null)
        {
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     item.Update(modelList, fields, tran, timeout);
                 }
             });
        }

        public override int UpdateIgnore(T model, List<string> fields, DistributedTransaction tran = null, int? timeout = null)
        {
            int count = 0;
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     count += item.UpdateIgnore(model, fields, tran, timeout);
                 }
             });
            return count;
        }

        public override void UpdateIgnore(IEnumerable<T> modelList, List<string> fields, DistributedTransaction tran = null, int? timeout = null)
        {
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     item.UpdateIgnore(modelList, fields, tran, timeout);
                 }
             });
        }

        #endregion

        #region updateAsync

        public override async Task<int> UpdateAsync(T model, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                int count = 0;
                foreach (var item in TableList)
                {
                    count += await item.UpdateAsync(model, fields, tran, timeout, cancellationToken);
                }
                return count;
            });
            return await task;
        }

        public override async Task UpdateAsync(IEnumerable<T> modelList, List<string> fields = null, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = WrapAsync(tran, async () =>
            {
                foreach (var item in TableList)
                {
                    await item.UpdateAsync(modelList, fields, tran, timeout, cancellationToken);
                }
            });
            await task;
        }

        public override async Task<int> UpdateIgnoreAsync(T model, List<string> fields, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                int count = 0;
                foreach (var item in TableList)
                {
                    count += await item.UpdateIgnoreAsync(model, fields, tran, timeout, cancellationToken);
                }
                return count;
            });
            return await task;
        }

        public override async Task UpdateIgnoreAsync(IEnumerable<T> modelList, List<string> fields, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                foreach (var item in TableList)
                {
                    await item.UpdateIgnoreAsync(modelList, fields, tran, timeout, cancellationToken);
                }
            });
            await task;
        }

        #endregion

        #region delete

        public override int Delete(object id, DistributedTransaction tran = null, int? timeout = null)
        {
            int count = 0;
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     count += item.Delete(id, tran, timeout);
                 }
             });
            return count;
        }


        public override int Delete(T model, DistributedTransaction tran = null, int? timeout = null)
        {
            int count = 0;
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     count += item.Delete(model, tran, timeout);
                 }
             });
            return count;
        }

        public override int DeleteByIds(object ids, DistributedTransaction tran = null, int? timeout = null)
        {
            int count = 0;
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     count += item.DeleteByIds(ids, tran, timeout);
                 }
             });
            return count;
        }

        public override void Delete(IEnumerable<T> modelList, DistributedTransaction tran = null, int? timeout = null)
        {
            Wrap(tran, () =>
             {
                 foreach (var item in TableList)
                 {
                     item.Delete(modelList, tran, timeout);
                 }
             });
        }

        #endregion

        #region deleteAsync

        public override async Task<int> DeleteAsync(object id, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                int count = 0;
                foreach (var item in TableList)
                {
                    count += await item.DeleteAsync(id, tran, timeout, cancellationToken);
                }
                return count;
            });
            return await task;
        }


        public override async Task<int> DeleteAsync(T model, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                int count = 0;
                foreach (var item in TableList)
                {
                    count += await item.DeleteAsync(model, tran, timeout, cancellationToken);
                }
                return count;
            });
            return await task;
        }

        public override async Task<int> DeleteByIdsAsync(object ids, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                int count = 0;
                foreach (var item in TableList)
                {
                    count += await item.DeleteByIdsAsync(ids, tran, timeout, cancellationToken);
                }
                return count;
            });
            return await task;
        }

        public override async Task DeleteAsync(IEnumerable<T> modelList, DistributedTransaction tran = null, int? timeout = null, CancellationToken cancellationToken = default)
        {
            var task = await WrapAsync(tran, async () =>
            {
                foreach (var item in TableList)
                {
                    await item.DeleteAsync(modelList, tran, timeout, cancellationToken);
                }
            });
            await task;
        }

        #endregion

        #region query

        public override Task<bool> ExistsAsync(object id, CancellationToken cancellationToken = default)
        {
            return Query.ExistsAsync(id, cancellationToken: cancellationToken);
        }

        public override Task<bool> ExistsAsync(T model, CancellationToken cancellationToken = default)
        {
            return Query.ExistsAsync(model, cancellationToken: cancellationToken);
        }

        public override Task<T> GetByIdAsync(object id, string returnFields = null, CancellationToken cancellationToken = default)
        {
            return Query.GetByIdAsync(id, returnFields, cancellationToken: cancellationToken);
        }

        public override async Task<IEnumerable<T>> GetByIdsAsync(object ids, string returnFields = null, CancellationToken cancellationToken = default)
        {
            return await Query.GetByIdsAsync(ids, returnFields, cancellationToken: cancellationToken);
        }

        #endregion
    }
}
